package com.bootdo.clouddo.core.security;

import com.bootdo.clouddo.utils.StringUtils;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.oauth2.common.ExpiringOAuth2RefreshToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.common.util.SerializationUtils;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.AuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.DefaultAuthenticationKeyGenerator;
import org.springframework.security.oauth2.provider.token.TokenStore;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;

//import org.apache.log4j.Logger;

/**
 * Acces Toke store for Redis server
 * @author Allen Wong
 *
 */
public class RedisAccessTokenStore implements TokenStore {

	private RedisTemplate<String, Object> redisTemplate;
	
	/**
	 * 每一个应用均需要对应一个app scope，用以和其他应用避免冲突
	 */
	public RedisAccessTokenStore(String appScopeKey){
		if(StringUtils.isNotEmpty(appScopeKey)){
			secAccessTokenKey = appScopeKey + secAccessTokenKey;
			secAuthenticationKey = appScopeKey + secAuthenticationKey;
			secRefreshTokenKey = appScopeKey + secRefreshTokenKey;
			secRefreshToken2AccessToken = appScopeKey + secRefreshToken2AccessToken;
			secClientId2AccessToken = appScopeKey + secClientId2AccessToken;
			secClientIdName2AccessToken = appScopeKey +secClientIdName2AccessToken;
			secAccessTokenExpiredKey = appScopeKey + secAccessTokenExpiredKey;
			secRefreshTokenExpiredKey = appScopeKey + secRefreshTokenExpiredKey;
			secClientId2AccessTokenExpiredKey = appScopeKey + secClientId2AccessTokenExpiredKey;
			secClientName2AccessTokenExpiredKey = appScopeKey + secClientName2AccessTokenExpiredKey;
		}
	}
	public RedisAccessTokenStore(){}
	
	
	/**
	 * 保存每一个accesstoken过期时间
	 */
	private String secAccessTokenExpiredKey = SEC_ACCESS_TOKEN_KEY + "_expirekey";

	/**
	 * 保存每一个refreshtoken过期时间
	 */
	private String secRefreshTokenExpiredKey = SEC_REFRESH_TOKEN_KEY + "_expirekey";
	
	/**
	 * clientid 的token过期时间
	 */
	private String secClientId2AccessTokenExpiredKey = SEC_CLIENT_ID_2_ACCESS_TOKEN + "_expirekey";
	
	/**
	 * clientname 的token过期时间
	 */
	private String secClientName2AccessTokenExpiredKey = SEC_CLIENT_NAME_2_ACCESS_TOKEN + "_expirekey";
	
	/**
	 * access token存储在Redis里面的域的key
	 */
	public static final String SEC_ACCESS_TOKEN_KEY = "sec_access_token_key";
	private String secAccessTokenKey = SEC_ACCESS_TOKEN_KEY;
	
	/**
	 * authentication token 存在在Redis里面的域的key
	 */
	public static final String SEC_AUTHENTICATION_KEY = "sec_authentication_key";
	private String secAuthenticationKey = SEC_AUTHENTICATION_KEY;
	
	/**
	 * refresh token 存在在Redis里面的域的Key
	 */
	public static final String SEC_REFRESH_TOKEN_KEY = "sec_refresh_token_key";
	private String secRefreshTokenKey = SEC_REFRESH_TOKEN_KEY; 
	
	/**
	 * 使用refresh_token保存access_token的域的key
	 * 	 
	 * */
	public static final String SEC_REFRESH_TOKEN_2_ACCESS_TOKEN = "sec_refresh_token_2_access_token";
	private String secRefreshToken2AccessToken = SEC_REFRESH_TOKEN_2_ACCESS_TOKEN;
	
	
	/**
	 * 使用client_id保存access_token的域的key
	 */
	public static final String SEC_CLIENT_ID_2_ACCESS_TOKEN = "sec_client_id_2_access_token";
	private String secClientId2AccessToken = SEC_CLIENT_ID_2_ACCESS_TOKEN;
	
	/**
	 * 使用client_id,user_name保存access_token的域的key
	 */
	public static final String SEC_CLIENT_NAME_2_ACCESS_TOKEN = "sec_client_id_name_2_access_token";
	private String secClientIdName2AccessToken = SEC_CLIENT_NAME_2_ACCESS_TOKEN;

	/**
	 * 存储在Redis里面的域的key所对应的value是一个map，这个是map里面access_token的key
	 */
	public static final String SEC_ACCESS_TOKEN_VALUE_MKEY = "access_token";
	
	/**
	 * 存储在Redis里面的域的key所对应的value是一个map，这个是map里面authentication_token的key
	 */
	public static final String SEC_AUTHENTICATION_TOKEN_VALUE_MKEY = "authentication_token";
	
	/**
	 * 存在Redis里面的域的key所对应的value是一个map，这个是map里面的refresh_token的key
	 */
	public static final String SEC_REFRESH_TOKEN_VALUE_MKEY = "refresh_token";
	
//	private Logger logger = Logger.getLogger(getClass());

	public RedisTemplate<String, Object> getRedisTemplate() {
		return redisTemplate;
	}

	public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
		this.redisTemplate = redisTemplate;
	}

	public AuthenticationKeyGenerator getAuthenticationKeyGenerator() {
		return authenticationKeyGenerator;
	}

	private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();



	public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
		this.authenticationKeyGenerator = authenticationKeyGenerator;
	}

	public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
		OAuth2AccessToken accessToken = null;
		final String key = authenticationKeyGenerator.extractKey(authentication);
			accessToken = redisTemplate.execute(new RedisCallback<OAuth2AccessToken>() {
				@Override
				public OAuth2AccessToken doInRedis(RedisConnection connection)
						throws DataAccessException {
					 	byte[] keyQuery = key.getBytes();  
		                byte[] value = connection.hGet(secAuthenticationKey.getBytes(), keyQuery);  
		                if (value == null) {
		                    return null;  
		                }  
		                Map<String, Object> _tokens = SerializationUtils.deserialize(value);  
		                return (OAuth2AccessToken) _tokens.get(RedisAccessTokenStore.SEC_ACCESS_TOKEN_VALUE_MKEY);
				}
				
			});

		if (accessToken != null
				&& !key.equals(authenticationKeyGenerator.extractKey(readAuthentication(accessToken.getValue())))) {
			removeAccessToken(accessToken.getValue());
			// Keep the store consistent (maybe the same user is represented by this authentication but the details have
			// changed)
			storeAccessToken(accessToken, authentication);
		}
		return accessToken;
	}

	public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
		String refreshToken = null;
		if (token.getRefreshToken() != null) {
			refreshToken = token.getRefreshToken().getValue();
		}
		
		if (readAccessToken(token.getValue())!=null) {
			removeAccessToken(token.getValue());
		}
		final OAuth2AccessToken fToken = token;
		final OAuth2Authentication fAuthentication = authentication;
		final String fTokenStr = extractTokenKey(token.getValue());
		final String fRefreshStr = extractTokenKey(refreshToken);
		final String fClientID =authentication.getOAuth2Request().getClientId();
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object doInRedis(RedisConnection connection)
					throws DataAccessException {
					Map<String, Object> tokenMap = new HashMap<String, Object>();
					tokenMap.put(SEC_ACCESS_TOKEN_VALUE_MKEY, fToken);
					tokenMap.put(SEC_AUTHENTICATION_TOKEN_VALUE_MKEY, fAuthentication);
					byte[] tokenKeyByte = fTokenStr.getBytes();
	                connection.hSetNX(secAccessTokenKey.getBytes(), tokenKeyByte, SerializationUtils.serialize(tokenMap));
	                connection.hSetNX(secRefreshToken2AccessToken.getBytes(), fRefreshStr.getBytes(), SerializationUtils.serialize(tokenMap));
	                
	                List<Map<String, Object>> clientIdTokenList = new ArrayList<Map<String,Object>>();
	                byte[] client2TokenListByte = connection.hGet(secClientId2AccessToken.getBytes(), fClientID.getBytes());
	                if(client2TokenListByte != null){
	                	clientIdTokenList = SerializationUtils.deserialize(client2TokenListByte);
	                }
	                clientIdTokenList.add(tokenMap);
	                connection.hSet(secClientId2AccessToken.getBytes(), fClientID.getBytes(), SerializationUtils.serialize(clientIdTokenList));//直接覆盖原来的client持有的token
	                
	                List<Map<String, Object>> clientNameTokenList = new ArrayList<Map<String,Object>>();
	                byte[] clientName2TokenListByte = connection.hGet(secClientIdName2AccessToken.getBytes(), getApprovalKey(fAuthentication).getBytes());
	                if(clientName2TokenListByte != null){
	                	clientNameTokenList = SerializationUtils.deserialize(clientName2TokenListByte);
	                }
	                clientNameTokenList.add(tokenMap);
	                connection.hSet(secClientIdName2AccessToken.getBytes(), getApprovalKey(fAuthentication).getBytes(), SerializationUtils.serialize(clientNameTokenList));//直接覆盖原来的client持有的token
	                

	                return 1L; 
			}
		});
	}
	
	public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
		final OAuth2RefreshToken fToken = refreshToken;
		final OAuth2Authentication fAuthentication = authentication;
		final String fTokenStr = extractTokenKey(refreshToken.getValue());
		
		redisTemplate.execute(new RedisCallback<Object>() {
			@Override
			public Object doInRedis(RedisConnection connection)
					throws DataAccessException {
					Map<String, Object> tokenMap = new HashMap<String, Object>();
					tokenMap.put(SEC_REFRESH_TOKEN_VALUE_MKEY, fToken);
					tokenMap.put(SEC_AUTHENTICATION_TOKEN_VALUE_MKEY, fAuthentication);
					byte[] tokenKeyByte = fTokenStr.getBytes();
	                connection.hSetNX(secRefreshTokenKey.getBytes(), tokenKeyByte, SerializationUtils.serialize(tokenMap));
	             
	                return 1L; 
			}
		});
	}

	public OAuth2AccessToken readAccessToken(String tokenValue) {
		OAuth2AccessToken accessToken = null;

		final String tokeStr = extractTokenKey(tokenValue);
		accessToken = redisTemplate.execute(new RedisCallback<OAuth2AccessToken>() {
			@Override
			public OAuth2AccessToken doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] objByte = connection.hGet(secAccessTokenKey.getBytes(), tokeStr.getBytes());
				if(objByte == null){
					return null;
				}
				else{
					Map<String, Object> rslt = SerializationUtils.deserialize(objByte);
					return (OAuth2AccessToken) rslt.get(RedisAccessTokenStore.SEC_ACCESS_TOKEN_VALUE_MKEY);
				}
			}
		});

		return accessToken;
	}

	public void removeAccessToken(OAuth2AccessToken token) {
		removeAccessToken(token.getValue());
	}

	public void removeAccessToken(String tokenValue) {
		final String fTokenStr = extractTokenKey(tokenValue);
		redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] mapByte1 = connection.hGet(secAccessTokenKey.getBytes(), fTokenStr.getBytes());
				Map<String, Object> rslt1 = SerializationUtils.deserialize(mapByte1);
				OAuth2Authentication au = (OAuth2Authentication) rslt1.get(RedisAccessTokenStore.SEC_AUTHENTICATION_TOKEN_VALUE_MKEY);
				OAuth2AccessToken token = (OAuth2AccessToken) rslt1.get(RedisAccessTokenStore.SEC_ACCESS_TOKEN_VALUE_MKEY);
				connection.hDel(secClientId2AccessToken.getBytes(), au.getOAuth2Request().getClientId().getBytes());
				connection.hDel(secRefreshToken2AccessToken.getBytes(), extractTokenKey(token.getRefreshToken().getValue()).getBytes());
				connection.hDel(secAccessTokenKey.getBytes(), fTokenStr.getBytes());
				connection.hDel(secClientIdName2AccessToken.getBytes(), getApprovalKey(au).getBytes());
				
				return 1l;
			}
		});
	}
	
	/**
	 * 批量移除accessToken
	 * @param accessTokenValueList
	 */
	public void patchRemoveAcceToken(List<String> accessTokenValueList){
		final List<String> tokenList = accessTokenValueList;
		
		redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection connection)
					throws DataAccessException {
				for(String tokenValue:tokenList){
					String fTokenStr = extractTokenKey(tokenValue);
					byte[] mapByte1 = connection.hGet(secAccessTokenKey.getBytes(), fTokenStr.getBytes());
					Map<String, Object> rslt1 = SerializationUtils.deserialize(mapByte1);
					OAuth2Authentication au = (OAuth2Authentication) rslt1.get(RedisAccessTokenStore.SEC_AUTHENTICATION_TOKEN_VALUE_MKEY);
					OAuth2AccessToken token = (OAuth2AccessToken) rslt1.get(RedisAccessTokenStore.SEC_ACCESS_TOKEN_VALUE_MKEY);
					connection.hDel(secClientId2AccessToken.getBytes(), au.getOAuth2Request().getClientId().getBytes());
					connection.hDel(secRefreshToken2AccessToken.getBytes(), extractTokenKey(token.getRefreshToken().getValue()).getBytes());
					connection.hDel(secAccessTokenKey.getBytes(), fTokenStr.getBytes());
					connection.hDel(secClientIdName2AccessToken.getBytes(), getApprovalKey(au).getBytes());
				}
				return 1l;
			}
		});
	}
	
	/**
	 * 批量移除refreshToken
	 * @param tokenList
	 */
	public void patchRemoveRefreshToken(List<String> tokenList){
		final List<String> refreshTokenList = tokenList;
		
		redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection connection)
					throws DataAccessException {
				for(String token:refreshTokenList){
					String fTokenStr = extractTokenKey(token);		
					connection.hDel(secRefreshTokenKey.getBytes(), fTokenStr.getBytes());
				}
				return 1l;
			}
		});
	}

	public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
		return readAuthentication(token.getValue());
	}

	public OAuth2Authentication readAuthentication(String token) {
		OAuth2Authentication authentication = null;
		final String fToken = extractTokenKey(token);
		authentication = redisTemplate.execute(new RedisCallback<OAuth2Authentication>() {
			@Override
			public OAuth2Authentication doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] rslt = connection.hGet(secAccessTokenKey.getBytes(), fToken.getBytes());
				if(rslt == null){
					return null;
				}
				else{
					Map<String, Object> rsltMap = SerializationUtils.deserialize(rslt);
					return (OAuth2Authentication) rsltMap.get(RedisAccessTokenStore.SEC_AUTHENTICATION_TOKEN_VALUE_MKEY);
				}
			}
		});
		if(authentication == null)
			removeAccessToken(token);
		return authentication;
	}

	public OAuth2RefreshToken readRefreshToken(String token) {
		OAuth2RefreshToken refreshToken = null;
		final String fToken = extractTokenKey(token);
		refreshToken = redisTemplate.execute(new RedisCallback<OAuth2RefreshToken>() {
			@Override
			public OAuth2RefreshToken doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] rslt = connection.hGet(secRefreshTokenKey.getBytes(), fToken.getBytes());
				if(rslt == null )
					return null;
				else{
					Map<String, Object> map = SerializationUtils.deserialize(rslt);
					return (OAuth2RefreshToken) map.get(RedisAccessTokenStore.SEC_REFRESH_TOKEN_VALUE_MKEY);
				}
			}
		});
		if(refreshToken == null)
			removeRefreshToken(token);
		return refreshToken;
	}

	public void removeRefreshToken(OAuth2RefreshToken token) {
		removeRefreshToken(token.getValue());
	}

	public void removeRefreshToken(String token) {
		final String fTokenStr = extractTokenKey(token);
		redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection connection)
					throws DataAccessException {
				connection.hDel(secRefreshTokenKey.getBytes(), fTokenStr.getBytes());
				return 1l;
			}
		});
	}
	
	public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
		return readAuthenticationForRefreshToken(token.getValue());
	}

	public OAuth2Authentication readAuthenticationForRefreshToken(String token) {
		OAuth2Authentication authentication = null;
		final String fToken = extractTokenKey(token);
		authentication = redisTemplate.execute(new RedisCallback<OAuth2Authentication>() {
			@Override
			public OAuth2Authentication doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] rslt = connection.hGet(secRefreshTokenKey.getBytes(), fToken.getBytes());
				if(rslt == null){
					return null;
				}
				else{
					Map<String, Object> rsltMap = SerializationUtils.deserialize(rslt);
					return (OAuth2Authentication) rsltMap.get(RedisAccessTokenStore.SEC_AUTHENTICATION_TOKEN_VALUE_MKEY);
				}
			}
		});
		if(authentication == null)
			removeAccessToken(token);
		return authentication;
	}

	public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
		removeAccessTokenUsingRefreshToken(refreshToken.getValue());
	}

	public void removeAccessTokenUsingRefreshToken(String refreshToken) {
		final String fRefreshToken = extractTokenKey(refreshToken);
		redisTemplate.execute(new RedisCallback<Long>() {
			@Override
			public Long doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] rslt = connection.hGet(secRefreshToken2AccessToken.getBytes(), fRefreshToken.getBytes());
				if(rslt != null){
					Map<String, Object> map = SerializationUtils.deserialize(rslt);
					OAuth2AccessToken accessToken = (OAuth2AccessToken) map.get(RedisAccessTokenStore.SEC_ACCESS_TOKEN_VALUE_MKEY);
					OAuth2Authentication au = (OAuth2Authentication) map.get(RedisAccessTokenStore.SEC_AUTHENTICATION_TOKEN_VALUE_MKEY);
					String clientID = au.getOAuth2Request().getClientId();
					connection.hDel(secClientId2AccessToken.getBytes(), clientID.getBytes());
					connection.hDel(secClientIdName2AccessToken.getBytes(), getApprovalKey(au).getBytes());
					removeAccessToken(accessToken);
				}
				connection.hDel(secRefreshToken2AccessToken.getBytes(), fRefreshToken.getBytes());
				return 1l;
			}
		});
	}

	public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
		List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>();

		final String fClientID = clientId;
		redisTemplate.execute(new RedisCallback<List<OAuth2AccessToken>>() {
			@Override
			public List<OAuth2AccessToken> doInRedis(RedisConnection connection)
					throws DataAccessException { 
				byte[] listByte = connection.hGet(secClientId2AccessToken.getBytes(), fClientID.getBytes());
				if(listByte != null){
					List<Map<String, Object>> mapList = SerializationUtils.deserialize(listByte); 
					List<OAuth2AccessToken> rslt = new ArrayList<OAuth2AccessToken>();
					for(Map<String, Object> m:mapList){
						rslt.add((OAuth2AccessToken)m.get(RedisAccessTokenStore.SEC_ACCESS_TOKEN_VALUE_MKEY));
					}
					return rslt;
				}
				else{
					return null;
				}
			}
		});
		accessTokens = removeNulls(accessTokens);
		return accessTokens;
	}

	public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
		List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>();

		final String fClientId = clientId;
		final String fUserName = userName;
		accessTokens = redisTemplate.execute(new RedisCallback<List<OAuth2AccessToken>>() {

			@Override
			public List<OAuth2AccessToken> doInRedis(RedisConnection connection)
					throws DataAccessException {
				byte[] rslt = connection.hGet(secClientIdName2AccessToken.getBytes(), getApprovalKey(fClientId, fUserName).getBytes());
				if(rslt != null){
					List<Map<String, Object>> clientNameTokenList = SerializationUtils.deserialize(rslt);
					List<OAuth2AccessToken> accessTokensList = new ArrayList<OAuth2AccessToken>();
					for(Map<String, Object> m:clientNameTokenList){
						accessTokensList.add((OAuth2AccessToken)m.get(RedisAccessTokenStore.SEC_ACCESS_TOKEN_VALUE_MKEY));
					}
					return accessTokensList;
				}
				else{
					return null;
				}
			}
		});
		accessTokens = removeNulls(accessTokens);

		return accessTokens;
	}

	private List<OAuth2AccessToken> removeNulls(List<OAuth2AccessToken> accessTokens) {
		List<OAuth2AccessToken> tokens = new ArrayList<OAuth2AccessToken>();
		for (OAuth2AccessToken token : accessTokens) {
			if (token != null) {
				tokens.add(token);
			}
		}
		return tokens;
	}
	
	private String getApprovalKey(OAuth2Authentication authentication) {
		String userName = authentication.getUserAuthentication() == null ? "" : authentication.getUserAuthentication()
				.getName();
		return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);
	}

	private String getApprovalKey(String clientId, String userName) {
		return clientId + (userName==null ? "" : ":" + userName);
	}

	protected String extractTokenKey(String value) {
		if (value == null) {
			return null;
		}
		MessageDigest digest;
		try {
			digest = MessageDigest.getInstance("MD5");
		}
		catch (NoSuchAlgorithmException e) {
			throw new IllegalStateException("MD5 algorithm not available.  Fatal (should be in the JDK).");
		}

		try {
			byte[] bytes = digest.digest(value.getBytes("UTF-8"));
			return String.format("%032x", new BigInteger(1, bytes));
		}
		catch (UnsupportedEncodingException e) {
			throw new IllegalStateException("UTF-8 encoding not available.  Fatal (should be in the JDK).");
		}
	}

	/**
	 * 【提供给定时任务使用】我们每个10分钟会去清一次已经过期的token
	 */
	public void flushExpiredToken(){
        List<String> toRemoveAccessTokenValueList = redisTemplate.execute(new RedisCallback<List<String>>() {
			@Override
			public List<String> doInRedis(RedisConnection connection)
					throws DataAccessException {
				List<String> _toRemoveTokenvalues = new ArrayList<String>();
				Map<byte[], byte[]> tokenWithKeyByteMap = connection.hGetAll(secAccessTokenKey.getBytes());
				Collection<byte[]> tokenValueByteMap = tokenWithKeyByteMap.values();
				for(byte[] mapByte:tokenValueByteMap){
					Map<String, Object> tokenMap = SerializationUtils.deserialize(mapByte);
					OAuth2AccessToken accessToken = (OAuth2AccessToken) tokenMap.get(SEC_ACCESS_TOKEN_VALUE_MKEY);
					if(accessToken.isExpired()){
						_toRemoveTokenvalues.add(accessToken.getValue());
					}
				}
				return _toRemoveTokenvalues;
			}
		});
        List<String> toRemoveRefreshTokenValueList = redisTemplate.execute(new RedisCallback<List<String>>() {
			@Override
			public List<String> doInRedis(RedisConnection connection)
					throws DataAccessException {
				List<String> _toRemoveTokenvalues = new ArrayList<String>();
				Map<byte[], byte[]> tokenWithKeyByteMap = connection.hGetAll(secRefreshTokenKey.getBytes());
				Collection<byte[]> tokenValueByteMap = tokenWithKeyByteMap.values();
				for(byte[] mapByte:tokenValueByteMap){
					Map<String, Object> tokenMap = SerializationUtils.deserialize(mapByte);
					OAuth2RefreshToken refreshToken = (OAuth2RefreshToken) tokenMap.get(SEC_REFRESH_TOKEN_VALUE_MKEY);
					if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
						ExpiringOAuth2RefreshToken expiringToken = (ExpiringOAuth2RefreshToken) refreshToken;
						if(expiringToken.getExpiration() == null
								|| System.currentTimeMillis() > expiringToken.getExpiration().getTime()){
							_toRemoveTokenvalues.add(expiringToken.getValue());
						}
					}
				}
				return _toRemoveTokenvalues;
			}
		});
        patchRemoveAcceToken(toRemoveAccessTokenValueList);
        patchRemoveRefreshToken(toRemoveRefreshTokenValueList);
	}


	protected byte[] serializeAccessToken(OAuth2AccessToken token) {
		return SerializationUtils.serialize(token);
	}

	protected byte[] serializeRefreshToken(OAuth2RefreshToken token) {
		return SerializationUtils.serialize(token);
	}

	protected byte[] serializeAuthentication(OAuth2Authentication authentication) {
		return SerializationUtils.serialize(authentication);
	}

	protected OAuth2AccessToken deserializeAccessToken(byte[] token) {
		return SerializationUtils.deserialize(token);
	}

	protected OAuth2RefreshToken deserializeRefreshToken(byte[] token) {
		return SerializationUtils.deserialize(token);
	}

	protected OAuth2Authentication deserializeAuthentication(byte[] authentication) {
		return SerializationUtils.deserialize(authentication);
	}

	public long getKey(){
		return redisTemplate.getExpire("test_access_token");
	}
	
}
